/**
  messages.c - Created by Timothy Morey on 5/13/2012
*/

#include <libxml/tree.h>
#include <netcdf.h>
#include <string.h>

#include "data-schema.h"
#include "err.h"
#include "layer.h"
#include "logger.h"
#include "messages.h"

#ifdef __WINDOWS__
#pragma warning(disable:4996)
#endif


/*****************************************************************************
 * Local definitions
 *****************************************************************************/

int CreateXmlDoc(MessageType type, const char* name, xmlDocPtr* doc, xmlNodePtr* root);

int DocToString(xmlDocPtr doc, char* buf, int* len);

int VerifyMessageType(xmlDocPtr doc, MessageType type, const char* name);


/*****************************************************************************
 * Implementation of messages.h functions
 *****************************************************************************/

int GetMessageInfo(const char* buf, int len, MessageType* type, char* name, int nameLen)
{
  int retval = NVN_NOERR;
  MessageType mtype = MessageTypeUnknown;

  if(buf)
  {
    if(len >= 5 && 0 == strncmp(buf, "<?xml", 5))
    {
      xmlDocPtr doc = xmlReadMemory(buf, len, "noname.xml", 0, 0);
	    if(doc)
	    {
		    xmlNodePtr root = xmlDocGetRootElement(doc);
        if(root)
        {
          if(0 == xmlStrncmp(BAD_CAST "Request", root->name, 7))
            mtype = MessageTypeRequest;
          else if(0 == xmlStrncmp(BAD_CAST "Response", root->name, 8))
            mtype = MessageTypeResponse;
          else
            mtype = MessageTypeUnknown;

          if(type)
            *type = mtype;

          if(name && mtype != MessageTypeUnknown)
          {
            xmlChar* xname = xmlGetProp(root, BAD_CAST "Name");
			      if(xname)
			      {
              if(nameLen >= xmlStrlen(xname))
                strcpy(name, (char*)xname);
              else
                strncpy(name, (char*)xname, nameLen);

              xmlFree(xname);
            }
          }
        }

        xmlFreeDoc(doc);
      }
      else
      {
        retval = NVN_ENOTAMSG;
      }
    }
    else
    {
      retval = NVN_EINVARGS;
    }
  }

  return retval;
}

int CreateAddLayerRequest(LayerProperties props, char* buf, int* len)
{
  int retval = NVN_NOERR;

  if(buf && len)
  {
    xmlDocPtr doc = 0;
    xmlNodePtr root = 0;
    xmlNodePtr child = 0;

    CreateXmlDoc(MessageTypeRequest, "AddLayer", &doc, &root);

    child = 0;
    SaveLayerProperties(props, &child);
    xmlAddChild(root, child);

    retval = DocToString(doc, buf, len);

    xmlFreeDoc(doc);
  }

  return retval;
}

int ParseAddLayerRequest(const char* msg, int len, LayerProperties* props)
{
  int retval = NVN_NOERR;
  
	xmlDocPtr doc = xmlReadMemory(msg, len, "noname.xml", 0, 0);
	if(doc)
	{
    if(VerifyMessageType(doc, MessageTypeRequest, "AddLayer"))
    {
		  xmlNodePtr root = xmlDocGetRootElement(doc);
      xmlNodePtr child = root->children;
      xmlChar* value = 0;

      while(child)
      {
        if(props && 0 == xmlStrcmp(BAD_CAST "LayerProperties", child->name))
        {
          ParseLayerProperties(child, props);
        }

        child = child->next;
      }
    }

    xmlFreeDoc(doc);
  }

  return retval;
}

int CreateAdjustDisplayRequest(int x, int y, int width, int height, int borderless, 
                               char* buf, int* len)
{
  int retval = NVN_NOERR;

  if(buf && len)
  {
    xmlDocPtr doc = 0;
    xmlNodePtr root = 0;
    xmlNodePtr child = 0;
    char temp[8];

    CreateXmlDoc(MessageTypeRequest, "AdjustDisplay", &doc, &root);

    child = xmlNewNode(0, BAD_CAST "X");
	  sprintf(temp, "%d", x);
    xmlSetProp(child, BAD_CAST "Value", BAD_CAST temp);
    xmlAddChild(root, child);

    child = xmlNewNode(0, BAD_CAST "Y");
	  sprintf(temp, "%d", y);
    xmlSetProp(child, BAD_CAST "Value", BAD_CAST temp);
    xmlAddChild(root, child);

    child = xmlNewNode(0, BAD_CAST "Width");
	  sprintf(temp, "%d", width);
    xmlSetProp(child, BAD_CAST "Value", BAD_CAST temp);
    xmlAddChild(root, child);

    child = xmlNewNode(0, BAD_CAST "Height");
	  sprintf(temp, "%d", height);
    xmlSetProp(child, BAD_CAST "Value", BAD_CAST temp);
    xmlAddChild(root, child);

    child = xmlNewNode(0, BAD_CAST "Borderless");
	  sprintf(temp, "%d", borderless);
    xmlSetProp(child, BAD_CAST "Value", BAD_CAST temp);
    xmlAddChild(root, child);

    retval = DocToString(doc, buf, len);

    xmlFreeDoc(doc);
  }

  return retval;
}

int ParseAdjustDisplayRequest(const char* msg, int len, int* x, int* y, int* width, int* height, int* borderless)
{
  int retval = NVN_NOERR;
  
	xmlDocPtr doc = xmlReadMemory(msg, len, "noname.xml", 0, 0);
	if(doc)
	{
    if(VerifyMessageType(doc, MessageTypeRequest, "AdjustDisplay"))
    {
		  xmlNodePtr root = xmlDocGetRootElement(doc);
      xmlNodePtr child = root->children;
      xmlChar* value = 0;

      while(child)
      {
        if(0 == xmlStrcmp(BAD_CAST "X", child->name))
        {
          value = xmlGetProp(child, "Value");
          *x = atoi((char*)value);
          xmlFree(value);
        }
        else if(0 == xmlStrcmp(BAD_CAST "Y", child->name))
        {
          value = xmlGetProp(child, "Value");
          *y = atoi((char*)value);
          xmlFree(value);
        }
        else if(0 == xmlStrcmp(BAD_CAST "Width", child->name))
        {
          value = xmlGetProp(child, "Value");
          *width = atoi((char*)value);
          xmlFree(value);
        }
        else if(0 == xmlStrcmp(BAD_CAST "Height", child->name))
        {
          value = xmlGetProp(child, "Value");
          *height = atoi((char*)value);
          xmlFree(value);
        }
        else if(0 == xmlStrcmp(BAD_CAST "Borderless", child->name))
        {
          value = xmlGetProp(child, "Value");
          *borderless = atoi((char*)value);
          xmlFree(value);
        }

        child = child->next;
      }
    }

    xmlFreeDoc(doc);
  }

  return retval;
}

int CreateCloseDisplayRequest(char* buf, int* len)
{
  int retval = NVN_NOERR;

  if(buf && len)
  {
    xmlDocPtr doc = 0;
    xmlNodePtr root = 0;

    CreateXmlDoc(MessageTypeRequest, "CloseDisplay", &doc, &root);
    retval = DocToString(doc, buf, len);

    xmlFreeDoc(doc);
  }

  return retval;
}

int CreateQueryDataGridSchemaRequest(const char* dataset, const char* varname, 
                                     char* buf, int* len)
{
  int retval = NVN_NOERR;

  if(buf && len)
  {
    xmlDocPtr doc = 0;
    xmlNodePtr root = 0;
    xmlNodePtr child = 0;

    CreateXmlDoc(MessageTypeRequest, "QueryDataGridSchema", &doc, &root);

    child = xmlNewNode(0, BAD_CAST "Dataset");
    xmlNewProp(child, BAD_CAST "Value", BAD_CAST dataset);
    xmlAddChild(root, child);

    child = xmlNewNode(0, BAD_CAST "Variable");
    xmlNewProp(child, BAD_CAST "Value", BAD_CAST varname);
    xmlAddChild(root, child);

    retval = DocToString(doc, buf, len);

    xmlFreeDoc(doc);
  }

  return retval;
}

int ParseQueryDataGridSchemaRequest(const char* msg, int len,
                                    char* dataset, int datasetlen,
                                    char* varname, int varnamelen)
{
  int retval = NVN_NOERR;

	xmlDocPtr doc = xmlReadMemory(msg, len, "noname.xml", 0, 0);
	if(doc)
	{
    if(VerifyMessageType(doc, MessageTypeRequest, "QueryDataGridSchema"))
    {
		  xmlNodePtr root = xmlDocGetRootElement(doc);
      xmlNodePtr child = root->children;
      while(child)
      {
			  if(0 == xmlStrcmp(BAD_CAST "Dataset", child->name))
			  {
				  xmlChar* xdataset = xmlGetProp(child, BAD_CAST "Value");
          strncpy(dataset, (char*)xdataset, datasetlen);
				  xmlFree(xdataset);
			  }
			  else if(0 == xmlStrcmp(BAD_CAST "Variable", child->name))
			  {
				  xmlChar* xvar = xmlGetProp(child, BAD_CAST "Value");
          strncpy(varname, (char*)xvar, varnamelen);
				  xmlFree(xvar);
			  }

				child = child->next;
      }
    }

    xmlFreeDoc(doc);
  }
  return retval;
}

int CreateQueryDataGridSchemaResponse(DataGridSchema schema, char* buf, int* len)
{
  int retval = NVN_NOERR;

  if(buf && len)
  {
    xmlDocPtr doc = 0;
    xmlNodePtr root = 0;
    xmlNodePtr node = 0;
    char* docstr = 0;

    CreateXmlDoc(MessageTypeResponse, "QueryDataGridSchema", &doc, &root);

    SaveDataGridSchema(schema, &node);
    xmlAddChild(root, node);

    retval = DocToString(doc, buf, len);

    xmlFreeDoc(doc);
  }

  return retval;
}

int ParseQueryDataGridSchemaResponse(const char* msg, int len, DataGridSchema* schema)
{
  int retval = NVN_NOERR;

	xmlDocPtr doc = xmlReadMemory(msg, len, "noname.xml", 0, 0);
	if(doc)
  {
    if(VerifyMessageType(doc, MessageTypeResponse, "QueryDataGridSchema"))
	  {
      xmlNodePtr root = xmlDocGetRootElement(doc);
      xmlNodePtr child = root->children;
      while(child)
      {
        if(0 == xmlStrcmp(BAD_CAST "DataGridSchema", child->name))
        {
          retval = ParseDataGridSchema(child, schema);
          break;
        }

        child = child->next;
      }
    }

    xmlFreeDoc(doc);
  }

  return retval;
}

int CreateQueryDataSetSchemaRequest(const char* dataset, char* buf, int* len)
{
  int retval = NVN_NOERR;

  if(buf && len)
  {
    xmlDocPtr doc = 0;
    xmlNodePtr root = 0;
    xmlNodePtr child = 0;

    CreateXmlDoc(MessageTypeRequest, "QueryDataSetSchema", &doc, &root);

    child = xmlNewNode(0, BAD_CAST "Dataset");
    xmlNewProp(child, BAD_CAST "Value", BAD_CAST dataset);
    xmlAddChild(root, child);

    retval = DocToString(doc, buf, len);

    xmlFreeDoc(doc);
  }

  return retval;
}

int ParseQueryDataSetSchemaRequest(const char* msg, int len,
                                   char* dataset, int datasetlen)
{
  int retval = NVN_NOERR;

	xmlDocPtr doc = xmlReadMemory(msg, len, "noname.xml", 0, 0);
	if(doc)
	{
    if(VerifyMessageType(doc, MessageTypeRequest, "QueryDataSetSchema"))
    {
		  xmlNodePtr root = xmlDocGetRootElement(doc);
      xmlNodePtr child = root->children;
      while(child)
      {
			  if(0 == xmlStrcmp(BAD_CAST "Dataset", child->name))
			  {
				  xmlChar* xdataset = xmlGetProp(child, BAD_CAST "Value");
          strncpy(dataset, (char*)xdataset, datasetlen);
				  xmlFree(xdataset);
			  }

				child = child->next;
      }
    }

    xmlFreeDoc(doc);
  }
  return retval;
}

int CreateQueryDataSetSchemaResponse(DataSetSchema schema, char* buf, int* len)
{
  int retval = NVN_NOERR;

  if(buf && len)
  {
    xmlDocPtr doc = 0;
    xmlNodePtr root = 0;
    xmlNodePtr child = 0;
    char* docstr = 0;

    CreateXmlDoc(MessageTypeResponse, "QueryDataSetSchema", &doc, &root);

    child = 0;
    SaveDataSetSchema(schema, &child);
    xmlAddChild(root, child);

    retval = DocToString(doc, buf, len);

    xmlFreeDoc(doc);
  }

  return retval;
}

int ParseQueryDataSetSchemaResponse(const char* msg, int len, DataSetSchema* schema)
{
  int retval = NVN_NOERR;

	xmlDocPtr doc = xmlReadMemory(msg, len, "noname.xml", 0, 0);
	if(doc)
  {
    if(VerifyMessageType(doc, MessageTypeResponse, "QueryDataSetSchema"))
	  {
      xmlNodePtr root = xmlDocGetRootElement(doc);
      xmlNodePtr child = root->children;
      while(child)
      {
        if(0 == xmlStrcmp(BAD_CAST "DataSetSchema", child->name))
        {
          retval = ParseDataSetSchema(child, schema);
          break;
        }

        child = child->next;
      }
    }

    xmlFreeDoc(doc);
  }

  return retval;
}

int CreateSimpleRequest(const char* name, const char* msg, char* buf, int* len)
{
  int retval = NVN_NOERR;

  if(buf && len)
  {
    xmlDocPtr doc = 0;
    xmlNodePtr root = 0;
    xmlNodePtr child = 0;

    CreateXmlDoc(MessageTypeRequest, name, &doc, &root);

    child = xmlNewNode(0, BAD_CAST "Message");
    xmlNewProp(child, BAD_CAST "Value", BAD_CAST msg);
    xmlAddChild(root, child);

    retval = DocToString(doc, buf, len);

    xmlFreeDoc(doc);
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int ParseSimpleRequest(const char* buf, int len, char* name, int namelen, char* msg, int msglen)
{
  int retval = NVN_NOERR;

  if(buf)
  {
    xmlDocPtr doc = xmlReadMemory(buf, len, "noname.xml", 0, 0);
    xmlNodePtr root = 0;
    xmlNodePtr child = 0;
    xmlChar* value = 0;

	  if(doc)
    {
      root = xmlDocGetRootElement(doc);
      if(0 == xmlStrcmp(BAD_CAST "Request", root->name))
      {
        value = xmlGetProp(root, BAD_CAST "Name");
        strncpy(name, (char*)value, namelen);
        xmlFree(value);
        
        child = root->children;
        while(child)
        {
          if(msg && 0 == xmlStrcmp(BAD_CAST "Message", child->name))
			    {
				    value = xmlGetProp(child, BAD_CAST "Value");
            strncpy(msg, (char*)value, msglen);
				    xmlFree(value);
			    }

				  child = child->next;
        }
      }

      xmlFreeDoc(doc);
    }
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int CreateSimpleResponse(int success, const char* name, const char* msg, 
                         char* buf, int* len)
{
  int retval = NVN_NOERR;

  if(buf && len)
  {
    xmlDocPtr doc = 0;
    xmlNodePtr root = 0;
    xmlNodePtr child = 0;
    char temp[8];

    CreateXmlDoc(MessageTypeResponse, name, &doc, &root);

    child = xmlNewNode(0, BAD_CAST "Success");
    sprintf(temp, "%d", success);
    xmlNewProp(child, BAD_CAST "Value", BAD_CAST temp);
    xmlAddChild(root, child);

    child = xmlNewNode(0, BAD_CAST "Message");
    xmlNewProp(child, BAD_CAST "Value", BAD_CAST msg);
    xmlAddChild(root, child);

    retval = DocToString(doc, buf, len);

    xmlFreeDoc(doc);
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int ParseSimpleResponse(const char* buf, int len, 
                        int* success, char* name, int namelen, 
                        char* msg, int msglen)
{
  int retval = NVN_NOERR;
  xmlDocPtr doc = 0;
  xmlNodePtr root = 0;
  xmlNodePtr child = 0;
  xmlChar* value = 0;

  if(buf)
  {
	  doc = xmlReadMemory(buf, len, "noname.xml", 0, 0);
	  if(doc)
	  {
      root = xmlDocGetRootElement(doc);
      if(0 == xmlStrcmp(BAD_CAST "Response", root->name))
      {
        if(name)
        {
          value = xmlGetProp(root, BAD_CAST "Name");
          strncpy(name, (char*)value, namelen);
          xmlFree(value);
        }

        child = root->children;
        while(child)
        {
			    if(success && 0 == xmlStrcmp(BAD_CAST "Success", child->name))
			    {
				    value = xmlGetProp(child, BAD_CAST "Value");
            *success = atoi((char*)value);
				    xmlFree(value);
			    }
			    else if(msg && 0 == xmlStrcmp(BAD_CAST "Message", child->name))
			    {
				    value = xmlGetProp(child, BAD_CAST "Value");
            strncpy(msg, (char*)value, msglen);
				    xmlFree(value);
			    }

				  child = child->next;
        }
      }

      xmlFreeDoc(doc);
    }
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}
                            
int CreateStartStreamRequest(int port, const char* dataset, const char* varname,
                             char* buf, int* len)
{
  int retval = NVN_NOERR;

  if(buf && len)
  {
    xmlDocPtr doc = 0;
    xmlNodePtr root = 0;
    xmlNodePtr node = 0;
    char temp[8];

    CreateXmlDoc(MessageTypeRequest, "StartStream", &doc, &root);

    node = xmlNewNode(0, BAD_CAST "Port");
	  sprintf(temp, "%d", port);
    xmlNewProp(node, BAD_CAST "Value", BAD_CAST temp);
    xmlAddChild(root, node);

    node = xmlNewNode(0, BAD_CAST "DatasetName");
    xmlNewProp(node, BAD_CAST "Value", BAD_CAST dataset);
    xmlAddChild(root, node);

    node = xmlNewNode(0, BAD_CAST "VariableName");
    xmlNewProp(node, BAD_CAST "Value", BAD_CAST varname);
    xmlAddChild(root, node);

    retval = DocToString(doc, buf, len);

    xmlFreeDoc(doc);
  }

  return retval;
}

int ParseStartStreamRequest(const char* msg, int len, int* port,
                            char* dataset, int datasetlen,
                            char* varname, int varnamelen)
{
  int retval = NVN_NOERR;

	xmlDocPtr doc = xmlReadMemory(msg, len, "noname.xml", 0, 0);
	if(doc)
  {
    if(VerifyMessageType(doc, MessageTypeRequest, "StartStream"))
	  {
		  xmlNodePtr root = xmlDocGetRootElement(doc);
		  xmlNodePtr child = root->children;
      xmlChar* value = 0;

		  while(child)
		  {
			  if(0 == xmlStrcmp(BAD_CAST "Port", child->name))
			  {
				  value = xmlGetProp(child, BAD_CAST "Value");
          *port = atoi((char*)value);
				  xmlFree(value);
			  }
        else if(0 == xmlStrcmp(BAD_CAST "DatasetName", child->name))
			  {
				  value = xmlGetProp(child, BAD_CAST "Value");
          strncpy(dataset, (char*)value, datasetlen);
				  xmlFree(value);
			  }
        else if(0 == xmlStrcmp(BAD_CAST "VariableName", child->name))
			  {
				  value = xmlGetProp(child, BAD_CAST "Value");
          strncpy(varname, (char*)value, varnamelen);
				  xmlFree(value);
			  }
            
        child = child->next;
		  }
	  }

		xmlFreeDoc(doc);
	}

  return retval;
}


/*****************************************************************************
 * Implementation of local functions
 *****************************************************************************/

int CreateXmlDoc(MessageType type, const char* name, xmlDocPtr* doc, xmlNodePtr* root)
{
  int retval = NVN_NOERR;

  if(name && doc && root)
  {
    char ctype[16];

    if(MessageTypeUnknown == type)
      strcpy(ctype, "Unknown");
    else if(MessageTypeRequest == type)
      strcpy(ctype, "Request");
    else if(MessageTypeResponse == type)
      strcpy(ctype, "Response");

    *doc = xmlNewDoc(BAD_CAST "1.0");
    *root = xmlNewNode(0, BAD_CAST ctype);
    xmlNewProp(*root, BAD_CAST "Name", BAD_CAST name);
    xmlDocSetRootElement(*doc, *root);
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int DocToString(xmlDocPtr doc, char* buf, int* len)
{
  int retval = NVN_NOERR;

  if(buf && len)
  {
    xmlChar* docStr = 0;
    int docLen = 0;
    xmlDocDumpMemory(doc, &docStr, &docLen);
    if(docStr)
    {
      if(docLen < *len)
      {
        strcpy(buf, (char*)docStr);
        *len = docLen;
      }
      else
      {
        *len = 0;
        WriteMessage(LogLevelError, "Message is too large to fit in the supplied buffer");
      }
      
      xmlFree(docStr);
    }
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int VerifyMessageType(xmlDocPtr doc, MessageType type, const char* name)
{
  int retval = 0;

	if(doc)
	{
		xmlNodePtr root = xmlDocGetRootElement(doc);
    char ctype[16];

    if(MessageTypeUnknown == type)
      strcpy(ctype, "Unknown");
    else if(MessageTypeRequest == type)
      strcpy(ctype, "Request");
    else if(MessageTypeResponse == type)
      strcpy(ctype, "Response");

		if(root && 0 == xmlStrncmp(BAD_CAST ctype, root->name, strlen(ctype)))
		{
			xmlChar* xname = xmlGetProp(root, BAD_CAST "Name");
			if(xname)
			{
				if(0 == xmlStrncmp(BAD_CAST name, xname, strlen(name)))
          retval = 1;

        xmlFree(xname);
      }
    }
  }

  return retval;
}
